﻿// ———————————————————————–
// <copyright file="FederationService.cs" company="The MITRE Corporation">
//    Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// </copyright>
// ———————————————————————–
/////////////////////////////////////////////////////////////////////////////////////////////////
// "FederationService".cs - "Forwards Messages to Internal or External Locations"
// Project: "ICNETSOA"- "FederationService"
//
// Language:    C#, .NET 4.0
// Platform:    Windows 7, VS 2010
// Author:      Don McGarry The MITRE Corporation
/////////////////////////////////////////////////////////////////////////////////////////////////

// Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//
// NOTICE
// This software was produced for the U. S. Government
// under Contract No. FA8721-09-C-0001, and is
// subject to the Rights in Noncommercial Computer Software
// and Noncommercial Computer Software Documentation Clause
// (DFARS) 252.227-7014 (JUN 1995)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Xml.XPath;
using System.Xml.Xsl;
using EDXLSharp;
using EDXLSharp.EDXLDELib;
using ICNETDAL;
using ICNETServices;
using log4net;

namespace FederationSvc
{
  /// <summary>
  /// Methods for processing a DE and figuring out to where it should be forwarded.
  /// </summary>
  public class FederationService : IRoutingICNET
  {

    private static ILog log= null;

    public FederationService()
    {
        if(log==null){
            log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
            log4net.Config.XmlConfigurator.Configure(new FileInfo(@"C:\ICNET\Config\FederationLogging.config"));
            log.Debug("Federation Service Started");
        }
    }
      
    /// <summary>
    /// Processes a DE and decies where to send it to, depending on the content keyword.
    /// </summary>
    /// <param name="distributionElement">The DE to be processed</param>
    [OperationBehavior(TransactionAutoComplete = true)]
    public void ProcessDE(EDXLDE distributionElement)
    {
      Binding bindingconfig;
      EndpointAddress remoteaddress;
      Uri tempuri;
      RouterClient client;
      try
      {
        // Clear out internal URN stuff
        for (int i = 0; i < distributionElement.SenderRole.Count; i++)
        {
          if (distributionElement.SenderRole[i].ValueListURN == ICNETSOAConstants.ICNETServicesURI)
          {
            distributionElement.SenderRole.RemoveAt(i);
            i--;
          }
        }

        List<string> templist = new List<string>();
        foreach (ContentObject co in distributionElement.ContentObjects)
        {
          templist.AddRange(RouterRulesDAL.GetDistinctFederationDestinations("ContentKeywordFed", co.ContentKeyword));
        }

        templist = templist.Distinct().ToList();
        if (templist.Count > 0)
        {
          foreach (string list in templist)
          {
            tempuri = new Uri(list);
            bindingconfig = null;
            switch (tempuri.Scheme)
            {
              case "net.msmq":
                distributionElement.SenderRole.Add(new ValueList(ICNETSOAConstants.ICNETfedURI, new List<string>() { ICNETSOAConstants.DEFedServiceNamespace }));
                bindingconfig = new NetMsmqBinding(NetMsmqSecurityMode.None);
                remoteaddress = new EndpointAddress(list);
                client = new RouterClient(bindingconfig, remoteaddress);
                log.Debug("Federating element with distributionID="+distributionElement.DistributionID+" and senderID="+distributionElement.SenderID+" to net.msmq URI " +remoteaddress);
                client.ProcessDE(distributionElement);
                break;
              case "http":
                log.Debug("Federating element with distributionID=" + distributionElement.DistributionID + " and senderID=" + distributionElement.SenderID + " to http URI " + tempuri);
                this.DoPost(tempuri, distributionElement);
                break;
              case "awsses":
                log.Debug("Preparing to email DE with distributionID: " + distributionElement.DistributionID + " and senderID: " + distributionElement.SenderID);
                this.EmailMsg(distributionElement);
                

                break;
            }
          }
        }
      }
      catch (Exception e)
      {
        ValueList servicerole = new ValueList();
        servicerole.ValueListURN = ICNETSOAConstants.ICNETServicesURI;
        servicerole.Value.Add(ICNETSOAConstants.ExceptionServiceNamespace);
        XNamespace ns = "http://tempuri.org";
        XElement xe = new XElement(ns + "Exception");
        xe.Add(new XElement("Message", e.Message));
        xe.Add(new XElement("StackTrace", e.StackTrace));
        xe.Add(new XElement("ErrorString", e.ToString()));
        if (e.InnerException != null)
        {
          xe.Add(new XElement("InnerMessage", e.InnerException.Message));
          xe.Add(new XElement("InnerErrorString", e.InnerException.ToString()));
        }

        ContentObject co = new ContentObject(ICNETSOAConstants.ICNETExceptionURI, new List<string>() { "Exception" });
        co.XMLContent = new XMLContentType();
        co.XMLContent.EmbeddedXMLContent.Add(xe);
        distributionElement.ContentObjects.Add(co);
        if (distributionElement.SenderRole.Count == 0)
        {
          distributionElement.SenderRole.Add(servicerole);
        }
        else
        {
          bool added = false;
          foreach (ValueList list in distributionElement.SenderRole)
          {
            if (list.ValueListURN == ICNETSOAConstants.ICNETServicesURI)
            {
              list.Value.Clear();
              list.Value.Add(ICNETSOAConstants.ExceptionServiceNamespace);
              added = true;
              break;
            }
          }

          if (!added)
          {
            distributionElement.SenderRole.Add(servicerole);
          }
        }

        client = new RouterClient();
        client.ProcessDE(distributionElement);
      }

      // RouterClient client;
      // client.ProcessDE(DEObj);
    }

    /// <summary>
    /// Forwards the DE to another webserver.
    /// </summary>
    /// <param name="requesturi">The location to where the DE should be forwarded.</param>
    /// <param name="distributionElement">The DE to be forwarded</param>
    private void DoPost(Uri requesturi, EDXLDE distributionElement)
    {
      HttpWebRequest request;
      HttpWebResponse resp;
      string s = distributionElement.WriteToXML();
      s = s.Replace("<?xml version=\"1.0\" encoding=\"utf-16\" standalone=\"no\"?>\r\n", String.Empty);
      request = (HttpWebRequest)WebRequest.Create(requesturi);
      request.KeepAlive = true;
      request.Method = "POST";
      request.ContentType = "text/xml";
      request.AllowAutoRedirect = true;
      request.ContentLength = Encoding.UTF8.GetByteCount(s);
      this.SetBody(request, s);
      resp = null;
      resp = (HttpWebResponse)request.GetResponse();
      resp.Close();
    }

    /// <summary>
    /// Function to perform HTTP POST
    /// </summary>
    /// <param name="request">Pending Web Request</param>
    /// <param name="requestBody">Body to POST</param>
    private void SetBody(HttpWebRequest request, string requestBody)
    {
      if (requestBody.Length > 0)
      {
        Stream requestStream = request.GetRequestStream();
        StreamWriter writer = new StreamWriter(requestStream);
        writer.AutoFlush = true;
        writer.Write(requestBody);
        writer.Flush();
        writer.Close();
        requestStream.Flush();
        requestStream.Close();
      }
    }

    /// <summary>
    /// Emails html version of the provided DE msg to a set of users
    /// </summary>
    /// <param name="distributionElement">DE to send</param>
    private void EmailMsg(EDXLDE distributionElement)
    {
      EmailInfo emailInfo = GetConfig();

      if (emailInfo != null)
      {
        try
        {
          SmtpClient client = null;

          if (!string.IsNullOrWhiteSpace(emailInfo.Host.Port))
          {
            int iPort;
            iPort = Int32.Parse(emailInfo.Host.Port);
            client = new SmtpClient()
            {
              Host = emailInfo.Host.Server,
              Port = iPort,
              EnableSsl = emailInfo.Host.EnableSSL,
              DeliveryMethod = SmtpDeliveryMethod.Network,
              Credentials = new NetworkCredential(emailInfo.Host.Sender.Username, emailInfo.Host.Sender.Password),
              Timeout = 20000
            };
          }
          else
          {
            client = new SmtpClient()
            {
              Host = emailInfo.Host.Server,
              EnableSsl = emailInfo.Host.EnableSSL,
              DeliveryMethod = SmtpDeliveryMethod.Network,
              Credentials = new NetworkCredential(emailInfo.Host.Sender.Username, emailInfo.Host.Sender.Password),
              Timeout = 20000
            };
          }

          log.Debug("Transforming DE");
          string sHTML = TransformXMLtoHTML(distributionElement.WriteToXML());

          foreach (Email email in emailInfo.EmailList)
          {
            // Create Message object
            MailMessage msg = new MailMessage(new MailAddress(email.From), new MailAddress(email.To, email.Fullname));
            //msg.CC.Add(new MailAddress(emailInfo.Host.Sender.Address));
            msg.Subject = "Field Report From " + distributionElement.SenderID;

            StringWriter sw = new StringWriter();
            
            msg.IsBodyHtml = true;
            msg.Body = sHTML;

            // Set the attachment
            byte[] byteArray = Encoding.ASCII.GetBytes(sHTML);
            MemoryStream stream = new MemoryStream(byteArray);

            msg.Attachments.Add(new Attachment(stream, msg.Subject + ".html", "text/html"));

            // Send email
            client.Send(msg);
            log.Debug("Emailed DE with distributionID: " + distributionElement.DistributionID + " and senderID: " + distributionElement.SenderID + " to email: " + email.To + ", " + email.Fullname);
          }
          
        }
        catch (SmtpException se)
        {
          log.Error("Email failed to send because of " + se.Message);
        }
        catch (System.FormatException)
        {
          log.Error("Unable to convert email server port: " + emailInfo.Host.Port + " to an Integer");
        }
        catch (System.ArgumentNullException)
        {
          log.Error("Email server port is null.");
        }
        catch (System.OverflowException)
        {
          log.Error("Unable to convert email server port: " + emailInfo.Host.Port + " to an Integer");
        }
      }
    }

    private EmailInfo GetConfig()
    {
      FederationConfig config = null;

      XmlSerializer s = new XmlSerializer(typeof(FederationConfig));
      TextReader r = null;
      String icnetRoot = null;

      icnetRoot = System.Environment.GetEnvironmentVariable("ICNET_ROOT", EnvironmentVariableTarget.Machine);

      if (icnetRoot == null)
      {
        System.Diagnostics.Debug.WriteLine("Missing ICNET_ROOT environment variable, defaulting to C:\\ICNET");
        Console.Write("Missing ICNET_ROOT environment variable, defaulting to C:\\ICNET");
        icnetRoot = @"C:\ICNET";
      }

      string fileName = icnetRoot + @"\Config\FederationConfig.xml";

      if (File.Exists(fileName))
      {
        r = new StreamReader(fileName);
      }
      else
      {
        log.Error("No config file: " + fileName + ". Make sure the ICNET_ROOT system environment variable is correct.");
        return null;
      }

      try
      {
        config = (FederationConfig)s.Deserialize(r);
      }
      catch (Exception)
      {
        log.Error("Config file unparsable: " + fileName);
        return null;
      }
      finally
      {
        r.Close();
      }

      return config.Emails;
    }

    /// <summary>
    /// Transforms a DE object in XML format into HTML.
    /// </summary>
    /// <param name="xe">The XML object to be transformed.</param>
    /// <param name="type">The type of DE message that is being passed in.</param>
    /// <returns>The stream containing the converted html</returns>
    private static string TransformXMLtoHTML(string sXML)
    {
      // Create the Stream to place the output.
      //System.IO.Stream str = new System.IO.MemoryStream();
      try
      {
        // Use .net XSLT libraries to conver the message XML to nice HTML
        System.Xml.Xsl.XslCompiledTransform xslt = new System.Xml.Xsl.XslCompiledTransform();
        XsltSettings xslt_settings = new XsltSettings();
        xslt_settings.EnableScript = true;
        xslt_settings.EnableDocumentFunction = true;

        XmlResolver resolver = new XmlUrlResolver();
        //resolver.Credentials = CredentialCache.DefaultCredentials;

        // Create an XsltArgumentList.
        XsltArgumentList xslArg = new XsltArgumentList();

        // Add an object to calculate the new book price.
        XsltFunctions obj = new XsltFunctions();
        xslArg.AddExtensionObject("http://csharp.org", obj);

        xslt.Load(@"C:\inetpub\wwwroot\XSLT\HTML DE.xsl", xslt_settings, resolver);

        XElement xe = XElement.Parse(sXML);
        StringWriter sw = new StringWriter();

        log.Debug("Calling transform");
        xslt.Transform(new XPathDocument(xe.CreateReader()), xslArg, sw);

        // Remember to Flush the Stream and set the position to 0
        //str.Flush();
        //str.Position = 0;

        return sw.ToString();
      }
      catch (Exception ex)
      {
        log.Error ("Error transforming DE", ex);
        throw;
      }
    }
  }

  public class XsltFunctions
  {
    public string GetAddressFromLatLon(string lat, string lon)
    {
      // if either is null, then assume we have nothing
      if (lat == null || lon == null)
      {
        lat = "0.0";
        lon = "0.0";
      }

      // this is the default string to send if we don't find a legit address
      string address = string.Format("Lat, Lon: {0}, {1}", lat, lon);

      try
      {
        // build the url to get the address from Google Geocoder
        string url = string.Format("http://maps.googleapis.com/maps/api/geocode/xml?latlng={0},{1}&sensor=false", lat, lon);

        XmlUrlResolver resolver = new XmlUrlResolver();
        resolver.Credentials = CredentialCache.DefaultCredentials;

        // parse the XML
        XmlDocument doc = new XmlDocument();
        doc.XmlResolver = resolver;
        doc.Load(url);

        // Make sure the status of the response is "OK"
        XmlNode statusNode = doc.SelectSingleNode("//GeocodeResponse/status");
        if (statusNode != null)
        {
          string status = statusNode.InnerText;
          if (string.Compare(status, "ok", true) == 0)
          {
            // Loop through the results
            XmlNodeList resultNodes = doc.SelectNodes("//GeocodeResponse/result");
            foreach (XmlNode resultNode in resultNodes)
            {
              XmlNode typeNode = resultNode.SelectSingleNode("type");
              if (typeNode != null)
              {
                // If the result type is a street address
                string type = typeNode.InnerText;
                if (string.Compare(type, "street_address", true) == 0 || string.Compare(type, "route", true) == 0)
                {
                  XmlNode addrNode = resultNode.SelectSingleNode("formatted_address");
                  if (addrNode != null)
                  {
                    // use the address given
                    address = addrNode.InnerText;
                    break;
                  }
                }
              }
            }
          }
        }
      }
      catch (Exception e)
      {
        // do nothing 
      }
      return address;
    }
  }

  [Serializable]
  [XmlRoot("federation")]
  public class FederationConfig
  {
    [XmlElement("emails")]
    public EmailInfo Emails;
  }

  [Serializable]
  [XmlRoot("emails")]
  public class EmailInfo
  {
    [XmlElement("host")]
    public HostInfo Host;
    
    [XmlElement("email")]
    public List<Email> EmailList;
  }

  [Serializable]
  [XmlRoot("host")]
  public class HostInfo
  {
    [XmlAttribute("server")]
    public string Server;

    [XmlAttribute("port")]
    public string Port;

    [XmlAttribute("enablessl", DataType="boolean")]
    public bool EnableSSL;

    [XmlElement("sender")]
    public SenderInfo Sender;
  }

  [Serializable]
  [XmlRoot("sender")]
  public class SenderInfo
  {
    [XmlAttribute("address")]
    public string Address;

    [XmlAttribute("username")]
    public string Username;

    [XmlAttribute("password")]
    public string Password;

    [XmlAttribute("key")]
    public string AccessKey;
  }

  [Serializable]
  [XmlRoot("email")]
  public class Email
  {
    [XmlAttribute("toAddress")]
    public string To;

    [XmlAttribute("toFullname")]
    public string Fullname;

    [XmlAttribute("fromAddress")]
    public string From;
  }
}
